use core::borrow;
use std::borrow::{Borrow, BorrowMut};
use std::cell::RefCell;
use std::rc::Rc;
use crate::{mi_assert, mimalloc::{atomic::*, internal::*, types::*, MI_SMALL_SIZE_MAX}, page, params::MI_MAX_ALIGN_SIZE};
use crate::params::*;
use crate::page::mi_page_use_delayed_free;

impl AsRef<MiPageQueue> for MiPageQueue {
    fn as_ref(&self) -> &MiPageQueue {
        self
    }
}


impl MiPageQueue {
    pub fn mi_page_queue_is_huge(self) -> bool {
        self.block_size == (MI_MEDIUM_OBJ_SIZE_MAX + std::mem::size_of::<usize>())
    }

    pub fn mi_page_queue_is_full(&self) -> bool {
        self.block_size == (MI_MEDIUM_OBJ_SIZE_MAX + (2 * std::mem::size_of::<usize>()))
    }

    pub fn mi_page_queue_is_special(&self) -> bool {
        self.block_size > MI_MEDIUM_OBJ_SIZE_MAX
    }
}

pub fn mi_bin(size: usize) -> u8 {
    let mut wsize = mi_wsize_from_size(size);

    let mut bin:u8;

    if wsize <= 1 {
        bin = 1;
    }else if wsize <= 4 && MI_MAX_ALIGN_SIZE > 2 * MI_INTPTR_SIZE {
        bin = ((wsize + 1) & !1) as u8;
    }
    else if wsize <= 8  && MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE{
        bin = ((wsize + 1) & !1) as u8;
    }
    else if wsize <= 8 {
        bin = wsize as u8;
    }
    else if wsize > MI_MEDIUM_OBJ_WSIZE_MAX {
        bin = MI_BIN_HUGE as u8;
    }
    else {
        if MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE {
            if wsize <= 16 {
                wsize =  (wsize + 3) & !3; 
            }
        }
        wsize -= 1;
        let b: u8 = mi_bsr(wsize) as u8;
        bin = (b << 2) + ((wsize>> (b - 2)) & 0x03) as u8 - 3;

        mi_assert!(bin < MI_BIN_HUGE as u8);
    }
    mi_assert!(bin >0 && bin <= MI_BIN_HUGE as u8);
    bin
}

pub fn _mi_bin(size: usize) -> u8 {
    return mi_bin(size);
}

pub fn _mi_bin_size(bin: u8) -> usize {
    unsafe {
        let heap = _MI_HEAP_EMPTY.as_ref().unwrap();
        (*heap.pages[bin as usize]).block_size
    }
}



pub fn mi_good_size(size: usize) -> usize {
    if size <= MI_MEDIUM_OBJ_SIZE_MAX {
        return _mi_bin_size(mi_bin(size + MI_PADDING_SIZE));
    }else{
        return mi_align_up(size + MI_PADDING_SIZE, _mi_os_page_size());
    }
}

impl MiPageQueue {
    pub fn mi_page_queue_contains(mut self, page: Option<Rc<RefCell<MiPage>>> ) -> bool {
        mi_assert!(page.is_some());
        let mut list = &self.first.clone();
        while list.is_some() {
            if let Some(temp_list) = list.clone() {
                let borrowed_temp = temp_list.as_ref().borrow(); 
                let nxt = &temp_list.clone().as_ref().borrow().next.clone();
                let prv = &temp_list.clone().as_ref().borrow().prev.clone();
                let tmp_prv = nxt.clone().unwrap().clone().as_ref().borrow().prev.clone();
                let tmp_nxt = prv.clone().unwrap().clone().as_ref().borrow().next.clone();
                mi_assert!(nxt.is_none()||tmp_prv.unwrap().as_ptr() == temp_list.as_ptr());
                mi_assert!(prv.is_none()||tmp_nxt.unwrap().as_ptr() == temp_list.as_ptr());
                if  temp_list.as_ptr() == page.clone().unwrap().as_ptr() {
                   self.first = nxt.clone();
                }
            }else{
                break;
            }
        }

        return self.first.unwrap().as_ptr() == page.unwrap().as_ptr();
    }
    
    pub fn mi_heap_contains_queue(mut self, page: Option<Rc<RefCell<MiPage>>>) -> bool {
        mi_assert!(page.is_some());
        let list = &self.first.clone();
        while list.is_some() {
            if let Some(temp_list) = list.clone() {
                let borrowed_temp = temp_list.as_ref().borrow(); 
                if temp_list.as_ptr() == page.clone().unwrap().as_ptr() {
                    self.first = borrowed_temp.next.clone();
                }
            }
        }
        return self.first.unwrap().as_ptr() == page.unwrap().as_ptr();
    }
}

impl MiPage {
    pub fn mi_page_is_large_or_huge(self) -> bool {
        return self.block_size > MI_MEDIUM_OBJ_SIZE_MAX || self.mi_page_is_huge();
    }

    pub fn mi_page_queue_of(self) -> Option<Rc<RefCell<MiPageQueue>>> {
    let heap = mi_page_heap(&self );
    if let Some(heap) = heap {
        let mut heap_mut = heap.as_ref().borrow_mut();
        let pq = heap_mut.mi_heap_page_queue_of(&self);
        return pq;
    } else {
        return None;
    }
}


}

impl MiHeap {
    pub fn mi_heap_page_queue_of(&mut self, page:& MiPage  ) -> Option<Rc<RefCell<MiPageQueue>>> {
        let binding = page.clone();
        let temp_page = & binding.borrow();
        let bin = if temp_page.mi_page_is_in_full() {MI_BIN_FULL} else {mi_bin(temp_page.mi_page_block_size()) as usize};
        mi_assert!(bin <= MI_BIN_FULL);
        let pq = self.pages[bin].clone().to_option_rc_refcell();
        return pq;
    }
}

// The current small page array is for efficiency and for each
// small size (up to 256) it points directly to the page for that
// size without having to compute the bin. This means when the
// current free page queue is updated for a small bin, we need to update a
// range of entries in `_mi_page_small_free`.

impl MiHeap {
    pub fn mi_heap_queue_first_update(&mut self, pq: Rc<RefCell<MiPageQueue>>) {
        let borrow_pq = pq.as_ref().borrow();
        let size = (&borrow_pq).block_size;
        if (size > MI_SMALL_SIZE_MAX) {
            return;
        }
        let page = (&borrow_pq).first.clone();
        let binding = page.clone().unwrap();
        let borrow_page = binding.as_ref().borrow();
        if page.is_some() {
            let mut start: usize;
            let idx = mi_wsize_from_size(size);
            let pages_free = &self.pages_free_direct;

           if pages_free[idx] == &*borrow_page as *const _ as *mut _ {
            return;
            }

            if (idx <= 1){
                start = 0;
            }
            else {
                let bin = mi_bin(size) as usize;
                //const mi_page_queue_t* prev = pq - 1;
                let cloned_pq = pq.clone();
                let temp_block_size = cloned_pq.as_ref().borrow().block_size;
                let prev = (&cloned_pq).as_ptr();
                unsafe {
                    while bin == mi_bin(temp_block_size) as usize && prev > self.pages[0]  {
                        prev.sub(1);
                    }
                    start = 1 + mi_bin(temp_block_size) as usize;
                }
                if start > idx {
                    start = idx;
                }
            }

            mi_assert!(start <= idx);
            for i in start..idx {
                self.pages_free_direct[i] = &borrow_page as *const _ as *mut _;
            }
        }
    }
}

impl MiPage{
    pub fn mi_page_queue_remove(mut self, pq: Rc<RefCell<MiPageQueue>>){
        let mut heap = mi_page_heap(&self);
        if self.prev.is_some() {
            self.prev.clone().unwrap().as_ref().borrow_mut().next = self.next.clone();
        }
        if self.next.is_some() {
            self.next.clone().unwrap().as_ref().borrow_mut().prev = self.prev.clone();
        }
        if &self as *const _ == pq.as_ref().borrow().last.clone().unwrap().as_ptr() {
            pq.as_ref().borrow_mut().last = self.prev.clone();
        }
        if &self as *const _ == pq.as_ref().borrow().first.clone().unwrap().as_ptr() {
            pq.as_ref().borrow_mut().first = self.next.clone();
        }
        if let Some(heap) = heap {
            let mut heap_ref = heap.as_ref().borrow_mut();
            heap_ref.page_count -= 1;
        }
        self.mi_page_set_in_full(false);
    }
}

impl MiHeap{
    pub fn mi_page_queue_push(mut self, pq: &mut MiPageQueue, page: & mut MiPage ) {
        page.mi_page_set_in_full(pq.mi_page_queue_is_full());
        page.next = pq.first.clone();
        page.prev = None;
        if pq.first.is_some() {
            pq.first.clone().unwrap().as_ref().borrow_mut().prev = page.clone().to_option_rc_refcell();
        }
        pq.first = page.clone().to_option_rc_refcell();
        if pq.last.is_none() {
            pq.last = page.clone().to_option_rc_refcell();
        }
        self.page_count += 1;
    }
}

pub fn mi_page_queue_enqueue_from(to: &mut MiPageQueue, from: &mut MiPageQueue, outer_page: Option<Rc<RefCell<MiPage>>> ){
    let binding = (&outer_page).clone().unwrap();
    let mut page = binding.as_ref().borrow_mut();
    let (prev, next) = (page.prev.clone(), page.next.clone());
    let bsize = page.mi_page_block_size();
    let heap = mi_page_heap(&mut page ); 
    if prev.is_some() {
        prev.clone().unwrap().as_ref().borrow_mut().next = next.clone();
    }
    if next.is_some() {
        next.clone().unwrap().as_ref().borrow_mut().prev = prev.clone();
    }

    if std::ptr::eq(outer_page.clone().unwrap().as_ptr(), from.last.clone().unwrap().as_ptr()) {
        from.last = prev.clone();
    }
    if std::ptr::eq(outer_page.clone().unwrap().as_ptr(), from.first.clone().unwrap().as_ptr()) {
        from.first = next.clone();
        if let Some(heap_ref) = heap.as_ref() {
            heap_ref.as_ref().borrow_mut().mi_heap_queue_first_update(from.clone().to_option_rc_refcell().unwrap());
        }
    }
    binding.as_ref().borrow_mut().prev = to.last.clone();
    binding.as_ref().borrow_mut().next = None;
    if to.last.is_some() {
        to.last.clone().unwrap().as_ref().borrow_mut().next = binding.as_ref().borrow().clone().to_option_rc_refcell();
        to.last = binding.as_ref().borrow().clone().to_option_rc_refcell();
    }
    if to.first.is_none() {
        to.first = binding.as_ref().borrow().clone().to_option_rc_refcell();
        to.last = binding.as_ref().borrow().clone().to_option_rc_refcell();
        if let Some(heap_ref) = heap.as_ref() {
            heap_ref.as_ref().borrow_mut().mi_heap_queue_first_update(to.clone().to_option_rc_refcell().unwrap());
        }
    }
    page.mi_page_set_in_full(to.mi_page_queue_is_full());
}

pub fn mi_page_queue_append(heap: &mut MiHeap, pq: &mut MiPageQueue,append: &mut MiPageQueue) -> usize {
    if append.first.is_none(){
        return 0;
    }
    let mut count = 0;
    let mut current_page = &append.first;
    while let Some(page) = current_page {
        let mut page_ref = page.as_ref().borrow_mut();
        mi_atomic_store_release(&page_ref.xheap, heap.as_mut_ptr() as usize);
        mi_page_use_delayed_free(&mut *page_ref, MiDelayed::MI_USE_DELAYED_FREE, false);
        count += 1;
    }

    if pq.last.is_some() {
        pq.first = append.first.clone();
        pq.last = append.last.clone();
        heap.mi_heap_queue_first_update(pq.to_option_rc_refcell().unwrap());
    }
    else{
        pq.last.clone().unwrap().as_ref().borrow_mut().next = append.first.clone();
        append.first.clone().unwrap().as_ref().borrow_mut().prev = pq.last.clone();
        pq.last = append.last.clone();
    }
    count
}